﻿#define DEBUG
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;


namespace LabTask2Sharp
{
    class ConstraintSatisfaction
    {
        public List<Room> Rooms { get; set; }
        public List<Person> Persons { get; set; }
        public int RoomFittingTests { get; set; }

        private Stopwatch timer = new Stopwatch();

        #region Construction&Initialization
        public ConstraintSatisfaction()
        {
            Rooms = new List<Room>();
            Persons = new List<Person>();
            RoomFittingTests = 0;
            InitLogging();
        }

        public void SetRoomNear(string roomId, List<string> nearIds)
        {
            foreach (var room in Rooms)
            {
                if (room.Id == roomId)
                {
                    foreach (var id in nearIds)
                    {
                        room.Near.Add(Rooms.Find(x => x.Id == id));
                    }
                    return;
                }
            }
        } 
        #endregion

        #region Printout
        public string ListAllocations()
        {
            string output = "";
            foreach (var room in Rooms)
            {
                output += room.Id + ", WorkPlaces: " + room.WorkPlaces + ", VisitorPlaces: " + room.VisitorPlaces + "\tNear: ";
                foreach (var r in room.Near)
                {
                    output += r.Id + ", ";
                }
                output += "\n";
                int totVisitors = 0;
                foreach (var person in room.Occupants)
                {
                    output += "\t" + person.Id + ", Profession: " + person.Profession.ToString() + ", Garlic lover: " + person.GarlicLover.ToString() +
                        ", Max num visitors: " + person.MaxVisitors.ToString() + "\n";
                    totVisitors += person.MaxVisitors;
                }
                output += "\tTotal number of visitors: " + totVisitors + "\n\n";
            }
            output += "Number of room fitting tests: " + RoomFittingTests + "\n";
            output += "Elapsed time: " + timer.ElapsedMilliseconds + "ms";
            return output;
        } 
        #endregion

        #region Allocation
        public bool Allocate(bool runNaive = true)
        { 	
            foreach (var room in Rooms)
            {
                room.Occupants.Clear();
            }
            foreach (var person in Persons)
            {
                person.AssignedToRoom = false;
            }
            RoomFittingTests = 0;
            timer.Reset();
            if (runNaive)
            {
                Logger("Naive CSP solving: ");
                timer.Start();
                bool result = AllocateNaive();
                timer.Stop();
                Logger("\n");
                return result;
            }
            else
            {
                Logger("Smart CSP solving: ");
                timer.Start();
                bool result = AllocateSmart();
                timer.Stop();
                Logger("\n");
                return result;
            }
        }

        public bool AllocateNaive()
        {
            int i = 0;
            while (Persons[i].AssignedToRoom)
            {
                i++;
                if (i >= Persons.Count)
                    return true;
            }
            foreach (var room in Rooms)
            {
                Logger("Testing " + Persons[i].Id + " in " + room.Id + "...");
                room.Occupants.Add(Persons[i]);
                Persons[i].AssignedToRoom = true;
                if (!CheckConstraints(Persons[i], room))
                {
                    room.Occupants.Remove(Persons[i]);
                    Persons[i].AssignedToRoom = false;
                }
                else if (AllocateNaive())
                    return true;
                else
                {
                    room.Occupants.Remove(Persons[i]);
                    Persons[i].AssignedToRoom = false;
                }
            }
            return false;
        }

        public bool AllocateSmart()
        {
            UpdatePossibleAlloc();
            Person person = GetLowestPossible();
            if (person == null)
                return true;
            foreach (var t in TestAllocations(person))
            {
                Logger("Testing " + person.Id + " in " + t.Item2.Id + "...");
                t.Item2.Occupants.Add(person);
                person.AssignedToRoom = true;
                if (!CheckConstraints(person, t.Item2))
                {
                    t.Item2.Occupants.Remove(person);
                    person.AssignedToRoom = false;
                }
                else if (AllocateSmart())
                    return true;
                else
                {
                    t.Item2.Occupants.Remove(person);
                    person.AssignedToRoom = false;
                }
            }
            return false;
        } 
        #endregion

        #region TestAllocation&MostConstrainedSelection
        private List<Tuple<int, Room>> TestAllocations(Person person)
        {
            List<Tuple<int, Room>> result = new List<Tuple<int, Room>>();
            foreach (var room in person.PossibleRooms)
            {
                room.Occupants.Add(person);
                person.AssignedToRoom = true;
                UpdatePossibleAlloc();
                int sumPossible = 0;
                bool okAssignment = true;
                foreach (var p in Persons)
                {
                    if (!p.AssignedToRoom)
                    {
                        if (p.PossibleRooms.Count == 0)
                        {
                            okAssignment = false;
                            break;
                        }
                        sumPossible += p.PossibleRooms.Count;
                    }
                }
                if (okAssignment)
                    result.Add(new Tuple<int, Room>(sumPossible, room));
                room.Occupants.Remove(person);
                person.AssignedToRoom = false;
            }
            result.OrderBy(x => x.Item1);
            return result;
        }

        private Person GetLowestPossible()
        {
            int lowestPossible = Rooms.Count + 1;
            Person lowestPossiblePerson = null;
            foreach (var person in Persons)
            {
                if (!person.AssignedToRoom && person.PossibleRooms.Count < lowestPossible)
                {
                    //if(lowestPossiblePerson != null)
                    //    Logger(person.Id + " " + person.PossibleRooms.Count + " more constrained than " + lowestPossiblePerson.Id + " " + lowestPossiblePerson.PossibleRooms.Count);
                    lowestPossiblePerson = person;
                    lowestPossible = person.PossibleRooms.Count;
                }
                else if(!person.AssignedToRoom && person.PossibleRooms.Count == lowestPossible)
                {
                    if (person.Profession == Person.Professions.ItSupport || person.Profession == Person.Professions.Secretary)
                    {
                        //if (lowestPossiblePerson != null)
                        //    Logger(person.Id + " " + person.PossibleRooms.Count + " more constrained than " + lowestPossiblePerson.Id + " " + lowestPossiblePerson.PossibleRooms.Count);
                        lowestPossiblePerson = person;
                        lowestPossible = person.PossibleRooms.Count;
                    }
                }
            }
            //if (lowestPossiblePerson != null)
            //    Logger("Selected " + lowestPossiblePerson.Id + " " + lowestPossiblePerson.PossibleRooms.Count);
            return lowestPossiblePerson;
        } 
        #endregion

        #region PossibleAllocations
        private void UpdatePossibleAlloc()
        {
            List<Person> agenda = new List<Person>();
            List<Person> bosses = new List<Person>();
            int bossesNotChecked = FillAgendaAndBosses(agenda, bosses);

            while (agenda.Count != 0)
            {
                Person person = agenda[0];
                agenda.RemoveAt(0);

                if (!person.AssignedToRoom)
                {
                    if ((person.Profession == Person.Professions.ItSupport || person.Profession == Person.Professions.Secretary))
                    {
                        if (bossesNotChecked == 0)
                            SecITPossibleAlloc(person, bosses);
                        else
                            agenda.Add(person);
                    }
                    else if (person.Profession == Person.Professions.Boss)
                    {
                        BossPossibleAlloc(person);
                        bossesNotChecked--;
                    }
                    else
                    {
                        WorkerPossibleAlloc(person);
                    }
                }
                else if (person.Profession == Person.Professions.Boss)
                    bossesNotChecked--;
            }
        }

        private int FillAgendaAndBosses(List<Person> agenda, List<Person> bosses)
        {
            int bossesNotChecked = 0;
            foreach (var person in Persons)
            {
                agenda.Add(person);
                if (person.Profession == Person.Professions.Boss)
                {
                    bossesNotChecked++;
                    bosses.Add(person);
                }
            }
            return bossesNotChecked;
        }

        private void SecITPossibleAlloc(Person person, List<Person> bosses)
        {
            person.PossibleRooms.Clear();
            foreach (var boss in bosses)
            {
                foreach (var room in boss.PossibleRooms)
                {
                    foreach (var near in room.Near)
                    {
                        if (!person.PossibleRooms.Contains(near))
                        {
                            int sumVisitors = 0;
                            foreach (var p in near.Occupants)
                            {
                                sumVisitors += p.MaxVisitors;
                            }
                            if (near.Occupants.Count < near.WorkPlaces && near.VisitorPlaces >= sumVisitors + person.MaxVisitors)
                                person.PossibleRooms.Add(near);
                        }
                    }
                }
            }
        }

        private void WorkerPossibleAlloc(Person person)
        {
            person.PossibleRooms.Clear();
            foreach (var room in Rooms)
            {
                int sumVisitors = 0;
                bool noBossInRoom = true;
                foreach (var p in room.Occupants)
                {
                    sumVisitors += p.MaxVisitors;
                    if (p.Profession == Person.Professions.Boss)
                        noBossInRoom = false;
                }
                if (noBossInRoom && room.Occupants.Count < room.WorkPlaces && room.VisitorPlaces >= sumVisitors + person.MaxVisitors)
                    person.PossibleRooms.Add(room);
            }
        }

        private void BossPossibleAlloc(Person person)
        {
            person.PossibleRooms.Clear();
            foreach (var room in Rooms)
            {
                if (room.Occupants.Count == 0 && room.VisitorPlaces >= 5)
                    person.PossibleRooms.Add(room);
            }
        } 
        #endregion

        #region Constraints
        private bool CheckConstraints(Person person, Room room)
        {
            RoomFittingTests++;
            if (!CheckRoomCapacity(person, room))
                return false;
            if (!CheckBossIsAlone(person, room))
                return false;
            if (!CheckSecretaryNearBoss(person, room))
                return false;
            if (!CheckItSupportNearBoss(person, room))
                return false;
            if (!CheckGarlicLovers(person, room))
                return false;
            if (!CheckVisitorSpace(person, room))
                return false;
            return true;
        }

        private bool CheckRoomCapacity(Person person, Room room)
        {
            if (room.Occupants.Count > room.WorkPlaces)
                return false;
            return true;
        }

        private bool CheckBossIsAlone(Person person, Room room)
        {
            if (person.Profession == Person.Professions.Boss)
            {
                if (room.Occupants.Count > 1)
                    return false;
            }
            else
            {
                foreach (var p in room.Occupants)
                {
                    if (p.Profession == Person.Professions.Boss)
                        return false;
                }
            }
            return true;
        }

        private bool CheckSecretaryNearBoss(Person person, Room room)
        {
            if (person.Profession != Person.Professions.Secretary)
                return true;
            foreach (var adjacent in room.Near)
            {
                foreach (var p in adjacent.Occupants)
                {
                    if (p.Profession == Person.Professions.Boss)
                        return true;
                }
            }
            return false;
        }

        private bool CheckItSupportNearBoss(Person person, Room room)
        {
            if (person.Profession != Person.Professions.ItSupport)
                return true;
            foreach (var adjacent in room.Near)
            {
                foreach (var p in adjacent.Occupants)
                {
                    if (p.Profession == Person.Professions.Boss)
                        return true;
                }
            }
            return false;
        }

        private bool CheckGarlicLovers(Person person, Room room)
        {
            foreach (var p in room.Occupants)
            {
                if (person.GarlicLover != p.GarlicLover)
                    return false;
            }
            return true;
        }

        private bool CheckVisitorSpace(Person person, Room room)
        {
            int numVisitors = 0;
            foreach (var p in room.Occupants)
            {
                numVisitors += p.MaxVisitors;
            }
            if (numVisitors > room.VisitorPlaces)
                return false;
            return true;
        } 
        #endregion

        #region DebuggingLogger
        [System.Diagnostics.Conditional("DEBUG")]
        public void InitLogging()
        {
            if (System.IO.File.Exists("../../csp.log"))
                System.IO.File.Delete("../../csp.log");
        }

        [System.Diagnostics.Conditional("DEBUG")]
        public void Logger(string msg)
        {
            System.IO.StreamWriter fout = System.IO.File.AppendText("../../csp.log");
            fout.WriteLine(msg);
            fout.Close();
        } 
        #endregion
    }
}
